import React, { useEffect, Fragment, useState, useMemo } from 'react';
import { Card, Button, message, Row, Col, Dropdown, Menu, Collapse, Icon } from 'antd';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { usePrevious } from '@umijs/hooks';
import memoizeOne from 'memoize-one';
import isEqual from 'lodash/isEqual';
import pick from 'lodash/pick';
import forEach from 'lodash/forEach';
import isObject from 'lodash/isObject';
import omit from 'lodash/omit';
import get from 'lodash/get';
import reject from 'lodash/reject';
import pickBy from 'lodash/pickBy';
import find from 'lodash/find';
import some from 'lodash/some';
import once from 'lodash/once';
import result from 'lodash/result';
import chunk from 'lodash/chunk';
import cloneDeep from 'lodash/cloneDeep';
import {
  Form,
  FormItem,
  FormButtonGroup,
  Submit,
  Reset,
  createFormActions,
  FormEffectHooks,
  MegaLayout,
} from '@formily/antd';
import {
  Input,
  Select,
  NumberPicker,
  Switch,
  DatePicker,
  TimePicker,
  Radio,
  Checkbox,
  Upload,
  Range,
  Transfer,
  Rating,
} from '@formily/antd-components';
// import { MegaLayout } from '@formily/antd/esm/components/FormMegaLayout';
import has from 'lodash/has';
import FooterToolbar from '@/components/FooterToolbar';
import request from '@/utils/request';
import ComboGrid from './ComboGrid';
import ComboModal from './ComboModal';
import SlickUpload from './SlickUpload';
import TreeSelect from './TreeSelect';
import StaticText from './StaticText';
import ButtonGroup from './ButtonGroup';
import AsyncComponent from './AsyncComponent';
import { getNode, hasKey, getNodeByName, filterHideItem, addRule } from './utils';
import DndItem from './DndItem';
import { itemTypes, foodTypes, bowlTypes, blockTypes, staticBlockTypes } from './types';
import styles from '../styles.less';
import { getUniqueID, closest } from '@/utils/utils';
// import 'antd/dist/antd.less';

const { Parser } = require('expr-eval');

const parser = new Parser({
  operators: {
    // These default to true, but are included to be explicit
    add: true,
    concatenate: true,
    conditional: true,
    divide: true,
    factorial: true,
    multiply: true,
    power: true,
    remainder: true,
    subtract: true,

    // Disable and, or, not, <, ==, !=, etc.
    logical: true,
    comparison: true,

    // Disable 'in' and = operators
    in: true,
    assignment: false,
  },
});

// 注册自定义方法 https://github.com/silentmatt/expr-eval#custom-javascript-functions
// 追加expiry缓存 是为了解决 初始化时联动规则多次触发 导致重复发请求
parser.functions.post = function(url, params) {
  return request(url, { data: params });
};

parser.functions.get = function(url, params) {
  return request(url, { method: 'get', data: params });
};

function removeElement(id) {
  const _element = document.getElementById(id);
  if (!_element) {
    return;
  }
  const _parentElement = _element.parentNode;
  if (_parentElement) {
    _parentElement.removeChild(_element);
  }
}

const components = {
  Input,
  TextArea: Input.TextArea,
  Radio: Radio.Group,
  RadioGroup: Radio.Group,
  Checkbox,
  CheckboxGroup: Checkbox.Group,
  NumberPicker,
  Select,
  Switch,
  DatePicker,
  RangePicker: DatePicker.RangePicker,
  YearPicker: DatePicker.YearPicker,
  MonthPicker: DatePicker.MonthPicker,
  WeekPicker: DatePicker.WeekPicker,
  TimePicker,
  Upload,
  Range,
  Rating,
  Transfer,
  ComboGrid,
  ComboModal,
  SlickUpload,
  TreeSelect,
  Async: AsyncComponent,
  StaticText,
  ButtonGroup,
};

const { onFieldValueChange$ } = FormEffectHooks;

const staticBlockSwitcher = (mode, item, extraProps = {}) => {
  let result;
  const { componentType } = item;
  switch (`${componentType}`.toLocaleLowerCase()) {
    // case 'steps':
    //   result = <Steps {...item} {...extraProps} />;
    //   break;
    case 'statictext':
      result = <StaticText mode={mode} {...item} {...extraProps} />;
      break;
    case 'buttongroup':
      result = <ButtonGroup mode={mode} {...item} {...extraProps} />;
      break;
    default:
      result = <AsyncComponent mode={mode} {...item} {...extraProps} />;
  }
  return result;
};

const formatNode = memoizeOne(node => {
  if (!isObject(node)) {
    return node;
  }

  const __componentType = `${node.componentType}`.toLocaleLowerCase();

  // 只保留formItem需要的字段，并剔除值为 null,[] 或 ''的无意义属性值
  let result = {
    ...pickBy(
      node,
      (value, key) =>
        value !== null &&
        value !== '' &&
        !isEqual(value, []) &&
        [
          'name',
          'label',
          'initialValue',
          'defaultValue',
          'placeholder',
          'disabled',
          'display',
          'rules',
          'dataSource',
          // 'labelCol',
          // 'wrapperCol'
          'megaProps',
          'uploadProps',
          'modalProps',
          'tableProps',
          'allowHalf',
          'editable',
        ].includes(key)
    ),
  };

  const { rules = {}, megaProps = { span: 1 }, ...rest } = result;

  /**
   * 对rules序列化
   *
   * 预期的node.rules格式{patternRule,requiredRule,formatRule}
   *
   * a、对rules.patternRule转义字符串类型的正则表达式
   * b、把rules从对象转成formItem能够接受的对象数组
   */

  const newRules = [];
  if (Object.keys(rules).length > 0) {
    // 转义字符串类型的正则表达式
    let rawRules = { ...rules };
    const { patternRule } = rawRules;

    if (patternRule) {
      const { pattern: str } = patternRule;
      let pattern;
      try {
        pattern = new RegExp(str.replace(/\//g, ''));
        // eslint-disable-next-line no-empty
      } catch (e) {}

      rawRules = { ...rawRules, patternRule: { ...patternRule, pattern } };
    }

    forEach(rawRules, value => {
      newRules.push(value);
    });
  }

  result = { ...result, rules: newRules };

  /**
   * 相应组件添加dataSource（需剔除冗余字段，否则FormItem报警告）
   */
  if (['checkboxgroup', 'radiogroup', 'select'].includes(__componentType)) {
    result = {
      ...result,
      dataSource: Array.isArray(node.dataSource)
        ? node.dataSource.map(option => pick(option, ['label', 'value']))
        : [],
    };
  } else {
    result = {
      ...omit(result, ['dataSource']),
    };
  }

  // 相应组件添加allowClear
  if (
    ![
      'checkboxgroup',
      'radiogroup',
      'upload',
      'switch',
      'combogrid',
      'combomodal',
      'rating',
    ].includes(__componentType)
  ) {
    result = { ...result, allowClear: true };
  } else {
    result = {
      ...omit(result, ['allowClear']),
    };
  }

  result = { ...omit(result, ['megaProps']), 'mega-props': megaProps };

  // upload设置initialValue 为 'fileList'
  // 详解：https://formilyjs.org/#/zoi8i0/6dt3t7FjI4
  if (__componentType === 'slickupload') {
    // raw = { ...raw, valueName: 'fileList' };
    if (isObject(result.uploadProps)) {
      result = { ...omit(result, ['uploadProps']), ...result.uploadProps };
    }
  }

  return result;
}, isEqual);

const SlickForm = ({
  mode,
  vertical,
  columnNum,
  labelWidth,
  formSchema,
  linkages,
  submitUrl,
  saveUrl,
  needBack,
  needSubmit,
  needSave,
  showHideItem,
  extraProps,
  initialValues,
  moveItem,
  onBack,
}) => {
  let megaLayoutExtraProps = {};

  const actions = useMemo(() => {
    return createFormActions();
  }, []);

  // const prevLinkages = usePrevious(linkages);

  function handleSave() {
    if (saveUrl) {
      const formValues = actions.getFormState().values;

      const submitData = { ...extraProps };
      submitData.objData = formValues;
      submitData.isDraft = true;

      request(saveUrl, { data: submitData }).then(res => {
        const { success, errMessage } = res;
        if (success) {
          message.success('提交成功');
        } else {
          message.error(errMessage || '提交失败');
        }
      });
    } else {
      message.warning('未配置保存地址');
    }
  }

  if (!vertical) {
    megaLayoutExtraProps = { labelWidth };
  }

  /**
   * 对表单内存在的FormItem逐个设置state
   *
   * state.value
   * state.rules
   * state.required
   * state.props.label
   * state.props.placeholder
   * state.props.disabled
   * state.props.dataSource
   * state.props.allowClear
   * state.props['mega-props']
   */
  useEffect(() => {
    const allField = actions.getFormGraph();
    // 获取当前表单上的所有formItem，需剔除holder是冗余的key
    const keys = Object.keys(allField).filter(val => val !== '' && !/^holder_\d*$/.test(val));

    keys.forEach(value => {
      const __node = getNodeByName(formSchema, value);
      const node = formatNode(__node);

      // 在删除的时候dva中的节点没了，但form中还存在
      if (isObject(node)) {
        const defaultValue = node.initialValue;
        let initialValue;

        if (isObject(initialValues)) {
          initialValue = initialValues[value];
        }

        actions.setFieldState([value], state => {
          /**
           * 1、
           * 初始值由两个因子决定，一个通过SlickForm的initialVaules传入，另一个是组件属性面板设置的默认值
           * 前者优先级大于后者
           */
          if (defaultValue !== undefined) {
            initialValue = defaultValue;
          }

          // 有效的initialValue
          if (
            initialValue !== undefined &&
            initialValue !== '' &&
            initialValue !== null &&
            !isEqual(initialValue, [])
          ) {
            state.value = initialValue;
          }

          // 重置 避免上一轮残留数据的污染（比如：“商机名称”设置初始值111，然后删除字段，再从左侧面板拖入“商机名称”）
          if (!initialValue && !defaultValue && state.value) {
            state.value = '';
          }

          state.rules = node.rules;

          // 3、required除了rules中体现，还必须在state上挂载
          state.required = !!find(node.rules, { required: true });
          if (__node && __node.componentType !== 'Async') {
            state.props.label = node.label;
          }

          state.props.placeholder = node.placeholder;
          state.props.disabled = node.disabled;

          // 相应组件添加dataSource（需剔除冗余字段，否则FormItem报警告）
          if (Array.isArray(node.dataSource) && node.dataSource.length > 0) {
            state.props.dataSource = node.dataSource;
          }

          // 相应组件添加allowClear
          if (has(node, 'allowClear')) {
            state.props.allowClear = node.allowClear;
          }

          state.props['mega-props'] = node['mega-props'];
        });
      }
    });
  }, [formSchema, initialValues, actions]);

  const formRender = (mode, formSchema, parentIndex = '') => {
    return Array.isArray(formSchema)
      ? formSchema
          .filter(item => {
            // 编辑模式 关闭“显示隐藏项”时，剔除
            if (mode === 'edit' && showHideItem === false && item.display === false) {
              return false;
            }
            return true;
          })
          .map((item, index) => {
            const isFood = some(
              foodTypes,
              val =>
                `${val.componentType}`.toLocaleLowerCase() ===
                `${item.componentType}`.toLocaleLowerCase()
            );

            // food类型
            if (isFood) {
              let raw = formatNode(item);
              let stylesProps = {};
              if (has(raw, 'mega-props.span') && raw['mega-props'].span) {
                stylesProps = { gridColumnStart: `span ${raw['mega-props'].span}` };
              }

              // 隐藏项被“编辑器开关”打开时
              if (raw.display === false && mode === 'edit') {
                stylesProps = { ...stylesProps, opacity: 0.4, background: '#e8e8e8' };
              }

              // 开关打开时，强制显示隐藏项
              if (mode === 'edit' && showHideItem === true) {
                raw = { ...raw, display: true };
              }

              if (
                ['combomodal', 'slickupload'].includes(`${item.componentType}`.toLocaleLowerCase())
              ) {
                raw = { ...raw, mode };
              }

              // 避免全屏场景，下拉框被遮挡
              if (
                [
                  'select',
                  'combogrid',
                  'datepicker',
                  'rangepicker',
                  'yearpicker',
                  'monthpicker',
                  'monthpicker',
                  'weekpicker',
                  'timepicker',
                  'treeselect',
                ].includes(`${item.componentType}`.toLocaleLowerCase())
              ) {
                raw = { ...raw, getPopupContainer: trigger => closest(trigger, '.js-ant-form') };
              }

              return (
                <Fragment key={item.__id}>
                  {mode === 'edit' ? (
                    <DndItem
                      data={item}
                      level={parentIndex === '' ? index : `${parentIndex}-${index}`}
                      type={itemTypes.food}
                      componentType={item.componentType}
                      moveItem={moveItem}
                      style={stylesProps}
                    >
                      <FormItem
                        {...raw}
                        triggerType="onChange"
                        component={components[`${item.componentType}`]}
                      />
                    </DndItem>
                  ) : (
                    <FormItem
                      {...raw}
                      triggerType="onChange"
                      component={components[`${item.componentType}`]}
                    />
                  )}
                </Fragment>
              );
            }

            const isBlock = some(
              blockTypes,
              val =>
                `${val.componentType}`.toLocaleLowerCase() ===
                `${item.componentType}`.toLocaleLowerCase()
            );

            // block类型
            if (isBlock) {
              return (
                <Fragment key={item.__id}>
                  {mode === 'edit' ? (
                    <DndItem
                      data={item}
                      level={index}
                      type={itemTypes.block}
                      componentType={item.componentType}
                      moveItem={moveItem}
                      className={styles.section}
                    >
                      <FormItem
                        {...omit(item, ['label', 'componentType'])}
                        {...extraProps}
                        mode={mode}
                        label={null}
                        triggerType="onChange"
                        component={components[`${item.componentType}`]}
                      />
                    </DndItem>
                  ) : (
                    <div className={styles.section}>
                      <FormItem
                        {...omit(item, ['label', 'componentType'])}
                        {...extraProps}
                        mode={mode}
                        label={null}
                        triggerType="onChange"
                        component={components[`${item.componentType}`]}
                      />
                    </div>
                  )}
                </Fragment>
              );
            }

            const isStaticBlock = some(
              staticBlockTypes,
              val =>
                `${val.componentType}`.toLocaleLowerCase() ===
                `${item.componentType}`.toLocaleLowerCase()
            );

            // staticBlock
            if (isStaticBlock) {
              return (
                <Fragment key={item.__id}>
                  {mode === 'edit' ? (
                    <DndItem
                      data={item}
                      level={index}
                      type={itemTypes.staticBlock}
                      componentType={item.componentType}
                      moveItem={moveItem}
                      className={styles.section}
                    >
                      {staticBlockSwitcher(mode, item, extraProps)}
                    </DndItem>
                  ) : (
                    <div className={styles.section}>
                      {staticBlockSwitcher(mode, item, extraProps)}
                    </div>
                  )}
                </Fragment>
              );
            }

            return (
              <Fragment key={item.__id}>
                {mode === 'edit' ? (
                  <DndItem
                    data={item}
                    level={index}
                    type={itemTypes.bowl}
                    componentType={item.componentType}
                    moveItem={moveItem}
                    className={styles.section}
                  >
                    {/* <Card
                      title={item.label}
                      className="cute"
                      bordered={false}
                      extra={<Icon type="down" onClick={e => console.log(e)} />}
                      bodyStyle={{ transition: 'all 0.3s' }}
                    > */}
                    <Collapse
                      className={classNames('cute')}
                      defaultActiveKey={['1']}
                      bordered={false}
                      expandIconPosition="right"
                    >
                      <Collapse.Panel header={item.label} key="1">
                        <MegaLayout
                          full
                          grid
                          autoRow
                          labelAlign={vertical ? 'top' : 'right'}
                          columns={columnNum}
                          {...megaLayoutExtraProps}
                        >
                          {formRender(mode, item.properties, index)}
                        </MegaLayout>
                      </Collapse.Panel>
                    </Collapse>
                    {/* </Card> */}
                  </DndItem>
                ) : (
                  // <Card
                  //   title={item.label}
                  //   className={classNames('cute', styles.section)}
                  //   bordered={false}
                  //   bodyStyle={{ transition: 'all 0.3s' }}
                  // >
                  <Collapse
                    className={classNames('cute', styles.section)}
                    defaultActiveKey={['1']}
                    bordered={false}
                    expandIconPosition="right"
                  >
                    <Collapse.Panel header={item.label} key="1">
                      <MegaLayout
                        full
                        grid
                        autoRow
                        labelAlign={vertical ? 'top' : 'right'}
                        columns={columnNum}
                        {...megaLayoutExtraProps}
                      >
                        {formRender(mode, item.properties, index)}
                      </MegaLayout>
                    </Collapse.Panel>
                  </Collapse>
                  // </Card>
                )}
              </Fragment>
            );
          })
      : null;
  };

  return (
    <Form
      // key={getUniqueID('form')}
      className={classNames(
        'js-ant-form',
        styles.slickForm,
        styles.megaLayoutFix,
        {
          [styles.editMode]: mode === 'edit',
          [styles.viewMode]: mode === 'view',
          [styles.readOnly]: `${mode}`.toLocaleLowerCase() === 'readonly',
        }
        // 'flow',
        // 'fix-label'
      )}
      onSubmit={values => {
        if (submitUrl) {
          const submitData = { ...extraProps };
          submitData.objData = values;
          submitData.isDraft = false;
          if (values.DealTacheSelect_orderObjRel) {
            submitData.orderObjRel = values.DealTacheSelect_orderObjRel;
            delete values.DealTacheSelect_orderObjRel;
          }

          request(submitUrl, { data: submitData }).then(res => {
            const { success, errMessage } = res;
            if (success) {
              message.success('提交成功');
            } else {
              message.error(errMessage || '提交失败');
            }
          });
        } else {
          message.warning('未配置提交地址');
        }
      }}
      editable={`${mode}`.toLocaleLowerCase() !== 'readonly'}
      previewPlaceholder={<span className="text-gray">无</span>}
      actions={actions}
      effects={({ setFieldState }) => {
        onFieldValueChange$('*').subscribe(({ name, value }) => {
          // // 避免初始化时重复执行
          // if (isEqual(prevLinkages, linkages)) {
          //   return;
          // }

          linkages.forEach(rule => {
            setFieldState([rule.targetEleCode], state => {
              let formValues = {};

              try {
                if (actions.getFormState().values) {
                  formValues = actions.getFormState().values;
                }
              } catch (e) {
                // throw e;
              }

              let passed;

              try {
                passed = parser.evaluate(rule.triggerEleRuleExpr, formValues);
              } catch (e) {
                // throw e;
              }

              if (`${rule.driveType}`.toLocaleLowerCase() === 'calc' && !!passed) {
                try {
                  // 【设置值】表达式中不允许使用post或get请求
                  if (
                    rule.targetEleRuleExpr.indexOf('post(') === -1 &&
                    rule.targetEleRuleExpr.indexOf('get(') === -1
                  ) {
                    state.value = parser.evaluate(rule.targetEleRuleExpr, formValues);
                  }
                } catch (e) {
                  // throw e;
                }
              }

              if (`${rule.driveType}`.toLocaleLowerCase() === 'setoptions' && !!passed) {
                try {
                  parser.evaluate(rule.targetEleRuleExpr, formValues).then(res => {
                    const { success, data, errorMessage } = res;
                    // console.log(data.map(item => pick(item, ['label', 'value'])));
                    if (success === true && Array.isArray(data)) {
                      // 必须再调用setFieldState方法 设置state.props.dataSource，因为异步会导致 赋值实现
                      setFieldState([rule.targetEleCode], state => {
                        state.props.dataSource = data.map(item => pick(item, ['label', 'value']));
                      });
                    } else {
                      message.warning(errorMessage || '回参异常');
                    }
                  });
                } catch (e) {
                  // throw e;
                }
              }

              if (`${rule.driveType}`.toLocaleLowerCase() === 'show') {
                state.visible = passed;
              }

              if (`${rule.driveType}`.toLocaleLowerCase() === 'hide') {
                state.visible = !passed;

                // 防止visible=false是，在画布中暂未
                if (passed) {
                  const node = getNodeByName(formSchema, rule.targetEleCode);
                  const el = document.getElementById(node.__id);
                  if (node.__id && el) {
                    // removeElement(node.__id);
                    el.style.display = 'none';
                  }
                }
              }

              if (`${rule.driveType}`.toLocaleLowerCase() === 'required') {
                if (passed) {
                  state.rules = Array.isArray(state.rules)
                    ? addRule(state.rules, 'required', {
                        required: true,
                        whitespace: true,
                        message: '必填项',
                      })
                    : { required: true, whitespace: true, message: '必填项' };
                } else if (Array.isArray(state.rules)) {
                  state.rules = reject(state.rules, { required: true });
                  state.required = false;
                }
              }

              if (`${rule.driveType}`.toLocaleLowerCase() === 'notRequired') {
                if (passed && Array.isArray(state.rules)) {
                  state.rules = reject(state.rules, { required: true });
                  state.required = false;
                } else {
                  state.rules = Array.isArray(state.rules)
                    ? addRule(state.rules, 'required', {
                        required: true,
                        whitespace: true,
                        message: '必填项',
                      })
                    : { required: true, whitespace: true, message: '必填项' };
                }
              }
            });
          });
        });
      }}
    >
      {formRender(mode, formSchema)}

      {needSubmit || needSave || needBack ? (
        <FormButtonGroup className={classNames(styles.footer, 'text-center')}>
          {needBack && <Button onClick={e => onBack(e)}>返回</Button>}
          {needSave && (
            <Button onClick={e => handleSave(e)} type="primary" ghost>
              保存
            </Button>
          )}
          {needSubmit && <Submit>提交</Submit>}
        </FormButtonGroup>
      ) : null}
    </Form>
  );
};

SlickForm.defaultProps = {
  vertical: true,
  columnNum: 3,
  labelWidth: 100,
  mode: 'view',
  moveItem: () => {},
  onBack: () => {},
  linkages: [],
  submitUrl: '',
  saveUrl: '',
  needSubmit: true,
  needSave: false,
  needBack: false,
  showHideItem: true,
  extraProps: {},
  initialValues: {},
};

SlickForm.propTypes = {
  formSchema: PropTypes.arrayOf(PropTypes.object).isRequired,
  vertical: PropTypes.bool,
  columnNum: PropTypes.number,
  labelWidth: PropTypes.number,
  mode: PropTypes.oneOf(['view', 'edit', 'readOnly']),
  moveItem: PropTypes.func,
  onBack: PropTypes.func,
  linkages: PropTypes.arrayOf(PropTypes.object),
  submitUrl: PropTypes.string,
  saveUrl: PropTypes.string,
  needSubmit: PropTypes.bool,
  needSave: PropTypes.bool,
  needBack: PropTypes.bool,
  showHideItem: PropTypes.bool,
  extraProps: PropTypes.object,
  initialValues: PropTypes.object,
};

export default SlickForm;
